/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package server;

import java.io.*;
import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import server.domain.Order;
import org.json.*;
import server.constants.ClientConstants;
import server.constants.GlobalConstants;
import server.constants.OrderConstants;
import server.constants.ProductConstants;
import server.domain.*;
import server.service.ClientService;
import server.service.OrderService;
import server.service.ProductService;

public class ServerTCP {

    //<editor-fold defaultstate="collapsed" desc="Global Variables">
    private static final int timeoutServer = 100000;
    private ServerSocket socket;
    private InputStream inputSocket;
    private OutputStream outpupSocket;
    private Socket socketConnection;
    PrintWriter pw;
    private static final String areaOrder = "3";
    private static final String areaCustomer = "1";
    private static final String areaProduct = "2";
    private String selectedMenu = "0";

    private enum Operation {

        CREATE, RETRIEVE, UPDATE, DELETE, EXECUTE
    }

    //</editor-fold>
    //<editor-fold defaultstate="collapsed" desc="Socket Stream">    
    public ServerTCP(int port, String adress) {
        try {
            //Init Socket
            bind(port, adress);
            realize();
        } catch (Exception e) {
            System.out.println(GlobalConstants.MESSAGE_GENERIC_ERROR);
        }
    }

    private void bind(int port, String adress) throws IOException {
        //Create a Server Socket
        socket = new ServerSocket();
        //Set the adress and port
        socket.bind(new InetSocketAddress(adress, port));
        //Set the timeout on the server
        socket.setSoTimeout(timeoutServer);
    }

    private void accept() {
        try {
            socketConnection = socket.accept();
            System.out.print("Conexão com o servidor aberta no endereço: " + socketConnection.getInetAddress().getHostName());
            //Get input
            inputSocket = socketConnection.getInputStream();
            //Get output
            outpupSocket = socketConnection.getOutputStream();
            pw = new PrintWriter(this.outpupSocket);
            pw.flush();
        } catch (IOException e) {
            System.out.println(GlobalConstants.MESSAGE_GENERIC_ERROR);
        }
    }

    private void realize() {
        if ((socket != null) && socket.isBound()) {
            //Format message
            String message = String.format("Servidor disponível, aguardando clientes na porta %s", this.socket.getLocalPort());
            //Show Message
            System.out.println(message);
            // Wait Client
            accept();
            //Init Menu
            getMenu("0");
            //Communication with the client
            conversarComCliente();
        }
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Transfer Logics">
    private void conversarComCliente() {
        String comando, resposta;
        do {
            comando = getCommand();
            resposta = processCommand(comando);
            sendResponse(resposta);
        } while (true);

    }

    private String getCommand() {
        String comandoRecebido;
        InputStreamReader isr = new InputStreamReader(this.inputSocket);
        BufferedReader br = new BufferedReader(isr);
        try {
            while (!br.ready()) {
            } //Wait
            comandoRecebido = br.readLine();
        } catch (IOException e) {
            comandoRecebido = e.getMessage();
        }

        return comandoRecebido;
    }

    private String processCommand(String comandoRecebido) {
        String resposta = "";
        resposta = comandoRecebido.toUpperCase();
        return resposta;
    }

    private void sendResponse(String resposta) {
        getMenu(resposta);
    }

    //</editor-fold> 
    //<editor-fold defaultstate="collapsed" desc="Product">
    private String generateLayoutProductObject(Product product) {
        if (product != null) {
            StringBuilder concat = new StringBuilder();
            concat.append("Id\t Descricao\t Preco\t Qtd.");
            concat.append("\n");
            concat.append(product.getId());
            concat.append("\t");
            concat.append(product.getDescription());
            concat.append("\t");
            concat.append(product.getPrice());
            concat.append("\t");
            concat.append(product.getStock());
            return concat.toString();
        } else {
            return ProductConstants.PRODUCT_NOT_FOUND;
        }
    }

    private String executeCommandProduct(String command, Operation operation) throws JSONException {
        JSONObject json = new JSONObject(command);
        switch (operation) {
            case CREATE:
                return new ProductService().insert(new Product(json.getString(ProductConstants.KEY_DESCRIPTION),
                        BigDecimal.valueOf(json.getDouble(ProductConstants.KEY_PRICE)),
                        json.getInt(ProductConstants.KEY_STOCK)));
            case UPDATE:
                return new ProductService().update(new Product(json.getString(ProductConstants.KEY_DESCRIPTION),
                        BigDecimal.valueOf(json.getDouble(ProductConstants.KEY_PRICE)),
                        json.getInt(ProductConstants.KEY_STOCK),
                        json.getLong(ProductConstants.KEY_ID)));
            case DELETE:
                if (json.length() > 1) {
                    return GlobalConstants.MESSAGE_INVALID_COMMAND;
                }
                return new ProductService().delete(new Product(json.getLong(ProductConstants.KEY_ID)));
            case RETRIEVE:
                if (json.length() > 1) {
                    return GlobalConstants.MESSAGE_INVALID_COMMAND;
                }
                if (!json.isNull(ProductConstants.KEY_DESCRIPTION)) {
                    List<Product> product = new ProductService().getByDescription(json.getString(ProductConstants.KEY_DESCRIPTION));
                    if (product.size() > 0) {
                        return generateLayoutList(product);
                    } else {
                        return ProductConstants.PRODUCT_NOT_FOUND;
                    }
                } else {
                    if (!json.isNull(ProductConstants.KEY_ID)) {
                        return generateLayoutProductObject(new ProductService().getById(json.getLong(ProductConstants.KEY_ID)));
                    }
                }
            default:
                return GlobalConstants.MESSAGE_INVALID_COMMAND;
        }
    }

    //</editor-fold> 
    //<editor-fold defaultstate="collapsed" desc="Client">
    private String generateLayoutClientObject(Client client) {
        if (client != null) {
            StringBuilder concat = new StringBuilder();
            concat.append("Id\t Nome\t CPF\t");
            concat.append("\n");
            concat.append(client.getId());
            concat.append("\t");
            concat.append(client.getName());
            concat.append("\t");
            concat.append(client.getCpf());
            return concat.toString();
        } else {
            return ClientConstants.CLIENT_NOT_FOUND;
        }
    }

    private String executeCommandClient(String command, Operation operation) throws JSONException {
        JSONObject json = new JSONObject(command);
        switch (operation) {
            case CREATE:
                return new ClientService().insert(new Client(json.getString(ClientConstants.KEY_NAME), json.getString(ClientConstants.KEY_CPF)));
            case UPDATE:
                return new ClientService().update(new Client(json.getString(ClientConstants.KEY_NAME), json.getString(ClientConstants.KEY_CPF), json.getLong(ClientConstants.KEY_ID)));
            case DELETE:
                if (json.length() > 1) {
                    return GlobalConstants.MESSAGE_INVALID_COMMAND;
                }
                return new ClientService().delete(new Client(json.getLong(ClientConstants.KEY_ID)));
            case RETRIEVE:
                if (json.length() > 1) {
                    return GlobalConstants.MESSAGE_INVALID_COMMAND;
                }

                if (!json.isNull(ClientConstants.KEY_ID.toString())) {
                    return generateLayoutClientObject(new ClientService().getById(json.getLong(ClientConstants.KEY_ID.toString())));
                } else {
                    if (!json.isNull(ClientConstants.KEY_NAME)) {
                        List<Client> client = new ClientService().getByName(json.getString(ClientConstants.KEY_NAME));
                        if (client.size() > 0) {
                            return generateLayoutList(client);
                        } else {
                            return ClientConstants.CLIENT_NOT_FOUND;
                        }
                    } else {
                        if (!json.isNull(ClientConstants.KEY_CPF)) {
                            return generateLayoutClientObject(new ClientService().getByCPF(json.getString(ClientConstants.KEY_CPF)));
                        }
                    }

                }
            default:
                return GlobalConstants.MESSAGE_INVALID_COMMAND;
        }
    }
    //</editor-fold>
    //<editor-fold defaultstate="collapsed" desc="Order">

    private String generateLayoutOrderObject(OrderDTO orderDTO) {
        SimpleDateFormat dt = new SimpleDateFormat("dd/MM/yyyy HH:mm");
        if (orderDTO != null) {
            StringBuilder concat = new StringBuilder();
            concat.append("Id\t Data Emissao\t Data Processamento\t Total \t Qtd. Produtos");
            concat.append("\n");
            concat.append(orderDTO.getId());
            concat.append("\t");
            concat.append(dt.format(orderDTO.getIssueDate()));
            concat.append("\t");
            concat.append(orderDTO.getProcessDate() != null ? dt.format(orderDTO.getProcessDate()) : "---");
            concat.append("\t");
            concat.append(orderDTO.getTotal());
            concat.append("\t");
            concat.append(orderDTO.getQtd());
            return concat.toString();
        } else {
            return OrderConstants.ORDER_NOT_FOUND;
        }
    }

    private String executeCommandOrder(String command, Operation operation) throws JSONException {
        JSONObject json = new JSONObject(command);
        JSONArray jsonOrderItem;
        List<OrderItem> itens;
        switch (operation) {
            case CREATE:

                jsonOrderItem = json.getJSONArray(OrderConstants.KEY_ITENS);
                long idClient = json.getLong(OrderConstants.KEY_CLIENT);
                itens = new ArrayList<OrderItem>();
                for (int i = 0; i < jsonOrderItem.length(); i++) {
                    long idProduct = jsonOrderItem.getJSONObject(i).getInt(OrderConstants.KEY_PRODUCT);
                    OrderItem orderItem = new OrderItem();
                    orderItem.setQuantity(jsonOrderItem.getJSONObject(i).getInt(OrderConstants.KEY_QUANTITY));
                    orderItem.setId(new PKOrderItem(new Order(), new Product(idProduct)));
                    itens.add(orderItem);
                }
                return new OrderService().insert(idClient, itens);
            case UPDATE:
                jsonOrderItem = json.getJSONArray(OrderConstants.KEY_ITENS);
                long idOrder = json.getLong(OrderConstants.KEY_ORDER);
                itens = new ArrayList<OrderItem>();
                for (int i = 0; i < jsonOrderItem.length(); i++) {
                    long idProduct = jsonOrderItem.getJSONObject(i).getInt(OrderConstants.KEY_PRODUCT);
                    OrderItem orderItem = new OrderItem();
                    orderItem.setQuantity(jsonOrderItem.getJSONObject(i).getInt(OrderConstants.KEY_QUANTITY));
                    orderItem.setId(new PKOrderItem(new Order(), new Product(idProduct)));
                    itens.add(orderItem);
                }
                return new OrderService().updateOrderItem(idOrder, itens);
            case DELETE:
                if (json.length() > 1) {
                    return GlobalConstants.MESSAGE_INVALID_COMMAND;
                }
                return new OrderService().delete(json.getLong(OrderConstants.KEY_ORDER));
            case RETRIEVE:
                if (json.length() > 1) {
                    return GlobalConstants.MESSAGE_INVALID_COMMAND;
                }

                if (!json.isNull(OrderConstants.KEY_CLIENT)) {
                    List<OrderDTO> listOrderClient = new OrderService().getByClient(json.getLong(OrderConstants.KEY_CLIENT));
                    if (listOrderClient.size() > 0) {
                        return generateLayoutList(listOrderClient);
                    } else {
                        return OrderConstants.ORDER_NOT_FOUND;
                    }
                } else {
                    if (!json.isNull(OrderConstants.KEY_ORDER)) {
                        return generateLayoutOrderObject(new OrderService().getByOrder(json.getLong(OrderConstants.KEY_ORDER)));
                    } else {
                        return GlobalConstants.MESSAGE_INVALID_COMMAND;
                    }
                }
            case EXECUTE:
                if (json.length() > 1) {
                    return GlobalConstants.MESSAGE_INVALID_COMMAND;
                }
                return new OrderService().processOrder(json.getLong(OrderConstants.KEY_ORDER));
            default:
                return GlobalConstants.MESSAGE_INVALID_COMMAND;
        }
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Layout and Command Helpers">
    public void getMenu(String request) {
        request = request.trim();

        if (request.equals("9")) {
            pw.println("Você esta saindo \n");
            try {
                socketConnection.close();
                System.out.println("\nSocket Fechado");
                realize();
            } catch (IOException ex) {
                System.out.println(ex.getMessage() + "\n");
            }

        } else {
            if (request.equals("0")) {

                selectedMenu = "0";
                String header = "Bem vindo ao nosso sistema, digite a área desejada: \n1-Cliente\n2-Produto\n3-Pedido\n9-Sair\n";
                pw.println(header);
            } else {
                int intMenu = Integer.parseInt(selectedMenu);
                if (intMenu > 0 && (request.equals(areaCustomer) || request.equals(areaOrder) || request.equals(areaProduct))) {
                    pw.println("Opção inválida\n");
                } else {
                    String header = "Você esta na área de %s. Digite 0 para voltar ao menu principal, digite 9 para sair ou insira o comando na linha abaixo \n";
                    if (request.equals(areaCustomer)) {
                        selectedMenu = areaCustomer;
                        pw.println(String.format(header, "clientes"));
                    } else {
                        if (request.equals(areaOrder)) {
                            selectedMenu = areaOrder;
                            pw.println(String.format(header, "pedido"));
                        } else {
                            if (request.equals(areaProduct)) {
                                selectedMenu = areaProduct;
                                pw.println(String.format(header, "produto"));
                            } else {
                                try {
                                    pw.println(extractCommand(request) + "\n");
                                } catch (JSONException ex) {
                                    pw.println(String.format("%s \n", GlobalConstants.MESSAGE_INVALID_COMMAND));
                                } catch (Exception ex) {
                                    pw.println(String.format("%s \n", GlobalConstants.MESSAGE_GENERIC_ERROR));
                                }
                            }
                        }
                    }
                }
            }
        }



        pw.flush();
    }

    public String extractCommand(String message) throws JSONException {
        String[] arrayCommand = message.split("/");
        String operation = arrayCommand[0].toLowerCase();

        if (operation.equals("add")) {
            return makeCommand(arrayCommand[1], Operation.CREATE);
        } else {
            if (operation.equals("alter")) {
                return makeCommand(arrayCommand[1], Operation.UPDATE);
            } else {
                if (operation.equals("del")) {
                    return makeCommand(arrayCommand[1], Operation.DELETE);
                } else {
                    if (operation.equals("get")) {
                        return makeCommand(arrayCommand[1], Operation.RETRIEVE);
                    } else {
                        if (operation.equals("exec") && selectedMenu.equals(areaOrder)) {
                            return makeCommand(arrayCommand[1], Operation.EXECUTE);
                        } else {
                            return GlobalConstants.MESSAGE_INVALID_COMMAND;
                        }
                    }
                }
            }
        }
    }

    private String generateLayoutList(List<?> list) {
        StringBuilder concat = new StringBuilder();
        boolean isHeader = true;
        for (Object item : list) {
            if (item instanceof Client) {
                if (isHeader) {
                    concat.append("Id\t Nome\t CPF\t");
                    isHeader = false;
                }
                Client client = (Client) item;
                concat.append("\n");
                concat.append(client.getId());
                concat.append("\t");
                concat.append(client.getName());
                concat.append("\t");
                concat.append(client.getCpf());
            }

            if (item instanceof Product) {
                if (isHeader) {
                    concat.append("Id\t Descricao\t Preco\t Estoque\t");
                    isHeader = false;
                }
                Product product = (Product) item;
                concat.append("\n");
                concat.append(product.getId());
                concat.append("\t");
                concat.append(product.getDescription());
                concat.append("\t");
                concat.append(product.getPrice());
                concat.append("\t");
                concat.append(product.getStock());
            }

            if (item instanceof OrderDTO) {
                if (isHeader) {
                    concat.append("Id\t Data Emissao\t Data Processamento\t Valor Total\t Qtd. Pedido \t Qtd. Produtos");
                    isHeader = false;
                }
                OrderDTO order = (OrderDTO) item;
                concat.append("\n");
                concat.append(order.getId());
                concat.append("\t");
                concat.append(order.getIssueDate());
                concat.append("\t");
                concat.append(order.getProcessDate());
                concat.append("\t");
                concat.append(order.getTotal());
                concat.append("\t");
                concat.append(order.getQtd());
            }

        }
        return concat.toString();
    }

    private String makeCommand(String command, Operation operation) throws JSONException {
        if (selectedMenu.equals(areaCustomer)) {
            return executeCommandClient(command, operation);
        } else {
            if (selectedMenu.equals(areaProduct)) {
                return executeCommandProduct(command, operation);
            } else {
                if (selectedMenu.equals(areaOrder)) {
                    return executeCommandOrder(command, operation);
                }
            }
        }
        return GlobalConstants.MESSAGE_INVALID_COMMAND;
    }
//</editor-fold>
}
